package net.jmchilton.spring.collect.spec;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import net.jmchilton.spring.collect.spec.namespace.Namespace;
import net.jmchilton.spring.collect.spec.namespace.Parameter;
import net.jmchilton.spring.collect.spec.namespace.StaticFactory;

import org.springframework.util.StringUtils;

/**
 * A wrapper around JAXB Namespace objects that 
 * adds some methods related to building schema.xsd
 * and static-factories.properties files required 
 * by the runtime infrastructure.
 * 
 * @author John Chilton
 *
 */
class Spec {
  private final Namespace namespace;

  Namespace getNamespace() {
    return this.namespace;
  }

  Spec(final File specFile) {
    this.namespace = NamespaceXmlUtils.getNamespace(specFile);
  }
  
  public static String pad(final String str, final String pad) {
    final String[] lines = str.split(System.getProperty("line.separator"));
    final StringBuilder paddedStr = new StringBuilder();
    for(String line : lines) {
      paddedStr.append(pad);
      paddedStr.append(line);
      paddedStr.append("\n");
    }
    return paddedStr.toString();
  }

  /**
   * @return The contents of the xsd file to be generated for this namespace.
   */
  String getContents() {
    final StringBuilder stringBuilder = new StringBuilder();
    /*
     * TODO: Add lists,sets, and maps back in for(StaticFactory element :
     * namespace.getList()) { stringBuilder.append("  <xsd:element name=\"" +
     * element.getElement() + "\" type=\"listOrSetBean\" />\n\n"); }
     * 
     * for(StaticFactory element : namespace.getSet()) {
     * stringBuilder.append("  <xsd:element name=\"" + element.getElement() +
     * "\" type=\"listOrSetBean\" />\n\n"); }
     * 
     * for(StaticFactory element : namespace.getMap()) {
     * stringBuilder.append("  <xsd:element name=\"" + element.getElement() +
     * "\n type=\"mapBean\" />\n\n"); }
     */
    for(final StaticFactory element : this.namespace.getStaticFactory()) {
      stringBuilder.append("  <!--\n");
      stringBuilder.append("  XSD definition corresponding to\n\n");
      stringBuilder.append(pad(NamespaceXmlUtils.toString(element), "  "));
      stringBuilder.append("\n  -->\n");
      stringBuilder.append("  <xsd:element name=\"" + element.getElement() + "\">\n");
      stringBuilder.append("    <xsd:annotation>\n");
      if(StringUtils.hasText(element.getDoc())) {
        stringBuilder.append("      <xsd:documentation>\n");
        stringBuilder.append("        " + element.getDoc());
        stringBuilder.append("      </xsd:documentation>\n");
      }
      stringBuilder.append("      <xsd:documentation>\n");
      stringBuilder.append("        Implemented via " + element.getClazz() + "." + element.getMethod() + " and produces bean of type " + element.getType() + "\n");
      stringBuilder.append("      </xsd:documentation>\n");      
      stringBuilder.append("      <xsd:appinfo>\n");
      stringBuilder.append("        <tool:annotation>\n");
      stringBuilder.append("          <tool:exports type=\""+ element.getType() +"\" />\n");
      stringBuilder.append("        </tool:annotation>\n");
      stringBuilder.append("      </xsd:appinfo>\n");
      stringBuilder.append("    </xsd:annotation>\n");        
      stringBuilder.append("    <xsd:complexType>\n");
      stringBuilder.append("      <xsd:complexContent>\n");
      stringBuilder.append("        <xsd:extension base=\"templateBean\">\n");
      if(element.getParameter() != null && element.getParameter().size() > 0) {
        stringBuilder.append("          <xsd:sequence>\n");
        for(final Parameter parameter : element.getParameter()) {
          stringBuilder.append("            <xsd:group ref=\"beans:collectionElements\" minOccurs=\"" + parameter.getMinNum() + "\" maxOccurs=\"" + parameter.getMaxNum() + "\">\n");
          stringBuilder.append("              <xsd:annotation>\n");
          if(StringUtils.hasText(parameter.getDoc())) {
            stringBuilder.append("                <xsd:documentation>\n");
            stringBuilder.append("                  " + parameter.getDoc() + "\n");
            stringBuilder.append("                </xsd:documentation>\n");
          }
          stringBuilder.append("                <xsd:documentation>\n");
          stringBuilder.append("                  Parameter(s) of type " + parameter.getType() + "\n");
          stringBuilder.append("                </xsd:documentation>\n");
          stringBuilder.append("                <xsd:appinfo>\n");
          stringBuilder.append("                  <tool:annotation>\n");
          stringBuilder.append("                    <tool:expected-type type=\"" +  parameter.getType() +"\" />\n");
          stringBuilder.append("                  </tool:annotation>\n");
          stringBuilder.append("                </xsd:appinfo>\n");
          stringBuilder.append("              </xsd:annotation>\n");
          stringBuilder.append("            </xsd:group>\n");
        }
        stringBuilder.append("          </xsd:sequence>\n");
      }
      stringBuilder.append("        </xsd:extension>\n");
      stringBuilder.append("      </xsd:complexContent>\n");
      stringBuilder.append("    </xsd:complexType>\n");
      stringBuilder.append("  </xsd:element>\n\n");
    }
    return stringBuilder.toString();
  }

  /**
   * 
   * @return The contents of the properties file required for the runtime
   *         reflection performed by the Spring namespace handlers.
   */
  String getPropertiesContents() {
    final StringBuilder builder = new StringBuilder();
    for(final Map.Entry<String, String> entry : this.getProperties().entrySet()) {
      builder.append(entry.getKey() + "=" + entry.getValue());
    }
    return builder.toString();
  }

  private Map<String, String> getProperties() {
    final Map<String, String> properties = new HashMap<String, String>();
    for(final StaticFactory element : this.namespace.getStaticFactory()) {
      final String clazz = element.getClazz();
      String method;
      if(element.getMethod() == null) {
        method = "";
      } else {
        method = element.getMethod();
      }
      properties.put(element.getElement(), clazz + "," + method + "\n");
    }
    return properties;
  }

}
